import simplejson
import random

import wsgiref.handlers

from google.appengine.ext import webapp
from google.appengine.ext import db
from google.appengine.api import memcache

import dict_en

class Msg(object):
    Error = 0
    
    GameRequestCreated = 10
    GameUpdate = 11
        
#------------------------------------------------------------------------------
# Base Handler
#------------------------------------------------------------------------------

class BaseHandler(webapp.RequestHandler):
    def __init__(self):
        super(BaseHandler, self).__init__()
        
    def render_json(self, msgType, obj):
        self.response.headers['Content-Type'] = 'application/json'
        
        if obj is not None:
            msg = {'msgType': msgType, 'msgContent': obj}
        else:
            msg = {'msgType': msgType}
        
        self.response.out.write(simplejson.dumps(msg))
    
    def render_error(self, obj):
        self.response.headers['Content-Type'] = 'application/json'        
        msg = {'errorInfo': obj}        
        self.response.out.write(simplejson.dumps(msg))
    
#------------------------------------------------------------------------------
# Game Cookies: How to get back to the game
#------------------------------------------------------------------------------

class Cookie(object):    
    @staticmethod
    def _base_n(num, base):
        # Works for 2 <= n <= 62
        return ((num == 0) and "0") or (Cookie._base_n(num // base, base).lstrip("0") + "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"[num % base])

    @staticmethod
    def _base_62(num):
        # A nice base to use -- over fifty billion values in just six characters. The power!
        return Cookie._base_n(num, 62)

    @staticmethod
    def random_cookie():
        # Random, but not necessarily unique
        return Cookie._base_62(random.randint(1, 50000000000))
    
    @staticmethod
    def unique_game_cookie():
        # Return two guaranteed-unique (and non-identical) cookies
        unique = False
        
        while not unique:
            one = Cookie.random_cookie()

            test_one = Game.all().filter('cookie =', one).get()

            unique = (test_one is None)
        
        return one

    @staticmethod
    def unique_player_cookie():
        # Return two guaranteed-unique (and non-identical) cookies
        unique = False
        
        while not unique:
            one = Cookie.random_cookie()
            test_one = Player.all().filter('cookie =', one).get()

            unique = (test_one is None)
            
            return one
                            

#------------------------------------------------------------------------------
# Models
#------------------------------------------------------------------------------

class ModelCache(object):
    @staticmethod
    def player_by_cookie(cookie):
        # comment this segment of code using memcache.
        # 1 compile error but can work,strange
        # 2 require flush correctly, not convenient
        # some time later will make this work for efficiency
        
        #player = memcache.get(cookie)
        #if player is not None:
        #    return player
        #else:
        #    player = Player.all().filter("cookie = ", cookie).get()
        #    if player is not None:
        #        memcache.set(cookie, player)
        #    return player
        
        return Player.all().filter("cookie = ", cookie).get()
        
class Game(db.Model):
    date_created = db.DateTimeProperty(auto_now_add = True) 
    
    roundIndex = db.IntegerProperty()
    finished = db.BooleanProperty()
    
    letters = db.StringListProperty()
    
    # Back reference the players
    player1_cookie = db.StringProperty()
    player2_cookie = db.StringProperty()    

    winner_cookie = db.StringProperty()
    
    cookie = db.StringProperty()
    
    @staticmethod
    def createGame(player1_cookie, player2_cookie):
        game = Game()
        game.player1_cookie = player1_cookie
        game.player2_cookie = player2_cookie
        game.roundIndex = 1
        game.finished = False
        game.generateLetters()
        game.cookie = Cookie.unique_game_cookie()
        
        game.put()
        
        return game
    
    def get_player1(self):
        return ModelCache.player_by_cookie(self.player1_cookie)

    def get_player2(self):
        return ModelCache.player_by_cookie(self.player2_cookie)
    
    def updateRound(self):
        if not self.finished:            
            if self.isRoundFinished():
                if self.roundIndex == 3:            
                    self.finished = True
                    self.put()
                else:                    
                    self.roundIndex += 1
                    self.generateLetters()
                    self.put()
                    
                    player1 = ModelCache.player_by_cookie(self.player1_cookie)
                    player1.roundIndex = self.roundIndex
                    player1.roundFinished = False
                    player1.put()                    
                    
                    player2 = ModelCache.player_by_cookie(self.player2_cookie)
                    player2.roundIndex = self.roundIndex
                    player2.roundFinished = False
                    player2.put()
                                          
        
    def generateLetters(self):
        letters = random.sample(dict_en.Dict.letters, 25)
        
        letterStr = ""
        for l in letters:
            letterStr += l.upper()
        
        self.letters.append(letterStr)
        
    def getCurrentRoundLetters(self):
        curRoundLetters = ''
        if self.roundIndex >= 1:
            curRoundLetters = self.letters[self.roundIndex - 1]
        
        return curRoundLetters
    
    def isRoundFinished(self):
        player1 = self.get_player1()
        player2 = self.get_player2()
        
        if player1.roundFinished and player2.roundFinished:
            return True
        else:
            return False
    
    def getStatAsJson(self):
        player1 = self.get_player1()
        player2 = self.get_player2()
        
        if self.finished:
            return {'finished': 1, 'gameCookie': self.cookie, 'curRound': self.roundIndex,'player1': player1.jsonObj(), 'player2': player2.jsonObj()}
        else:
            return {'finished': 0, 'gameCookie': self.cookie, 'curRound': self.roundIndex,'player1': player1.jsonObj(), 'player2': player2.jsonObj(), 'curLetters': self.getCurrentRoundLetters()}
    
class Player(db.Model):    
    cookie = db.StringProperty()
    name = db.StringProperty()
    
    gameCookie = db.StringProperty()
    
    roundIndex = db.IntegerProperty()
    roundFinished = db.BooleanProperty()
    
    roundScores = db.ListProperty(int)
    
    def jsonObj(self):
        return {'cookie': self.cookie, 'name': self.name, 'gameCookie': self.gameCookie, 'roundScores': {"s1": self.roundScores[0], "s2": self.roundScores[1], "s3": self.roundScores[2]}}
        
#------------------------------------------------------------------------------
# "RequestNewGame" Handler
#------------------------------------------------------------------------------
class RequestNewGameHandler(BaseHandler):
    def __init__(self):
        super(RequestNewGameHandler, self).__init__()

    def find_random(self):
        # Create cookies for accessing the game
        opponent = Player.all().filter('gameCookie =', None).get()
        
        return opponent

    def get(self, *args):
        your_name = self.request.GET.get('your_name')
        
        # Create cookies for accessing the game
        yourCookie = Cookie.unique_player_cookie()
        
        # Create your player
        yourPlayer = Player()
        yourPlayer.cookie = yourCookie
        yourPlayer.name = your_name
        yourPlayer.roundIndex = 0
        yourPlayer.roundFinished = False
        yourPlayer.roundScores.append(-1)
        yourPlayer.roundScores.append(-1)
        yourPlayer.roundScores.append(-1)
        
        opponentPlayer = self.find_random()        
        
        if opponentPlayer is None:
            yourPlayer.put()
            
            jsonRet = {'yourCookie': yourCookie}
            self.render_json(Msg.GameRequestCreated, jsonRet)
        else:
            game = Game.createGame(yourPlayer.cookie, opponentPlayer.cookie)
            
            yourPlayer.gameCookie = game.cookie
            yourPlayer.roundIndex = 1
            yourPlayer.put()
            
            opponentPlayer.gameCookie = game.cookie
            opponentPlayer.roundIndex = 1
            opponentPlayer.put()
        
            self.render_json(Msg.GameUpdate, {'yourCookie': yourCookie, 'gameCookie': game.cookie})


class UpdateRequestHandler(BaseHandler):
    def __init__(self):
        super(UpdateRequestHandler, self).__init__()
        
    def get(self, *args):
        yourCookie = self.request.GET.get('yourCookie')
         
        player = ModelCache.player_by_cookie(yourCookie)
        if player is None:
            self.render_error("find no player")
            return
        
        if player.gameCookie is None:
            self.render_json(Msg.GameRequestCreated, None)
            return
        
        game = Game.all().filter("cookie =", player.gameCookie).get()        
        if game is None:
            self.render_error("game is removed before you request starts")
            return

        self.render_json(Msg.GameUpdate, {'gameCookie': game.cookie})
        
#------------------------------------------------------------------------------
# Refresh Game    
#------------------------------------------------------------------------------

class UpdateResultHandler(BaseHandler):
    def __init__(self):
        super(UpdateResultHandler, self).__init__()
        
    def get(self, *args):
        gameCookie = self.request.GET.get('gameCookie')
        yourCookie = self.request.GET.get('yourCookie')
         
        game = Game.all().filter("cookie =", gameCookie).get()
        if not game:
            self.render_error("find no game")
            return
        
        player = ModelCache.player_by_cookie(yourCookie)
        
        if game.cookie != player.gameCookie:
            self.render_error("your cookie is not correct")
            return

        jsonRet = game.getStatAsJson()
                                        
        self.render_json(Msg.GameUpdate, jsonRet)
        

class SendResultHandler(BaseHandler):
    def __init__(self):
        super(SendResultHandler, self).__init__()
        
    def get(self, *args):
        gameCookie = self.request.GET.get('gameCookie')
        yourCookie = self.request.GET.get('yourCookie')
        yourScore = self.request.GET.get('yourScore')
         
        game = Game.all().filter("cookie =", gameCookie).get()
        if not game:
            self.render_error("find no player")
            return
        
        player1 = game.get_player1()
        player2 = game.get_player2()
        
        yourPlayer = None
        
        if player1.cookie == yourCookie:
            yourPlayer = player1
        else:
            yourPlayer = player2
        
        if not yourPlayer.roundFinished:
            yourPlayer.roundScores[yourPlayer.roundIndex -1] = int(yourScore)
            yourPlayer.roundFinished = True
            yourPlayer.put()
        
        game.updateRound()
                                                
        self.render_json(Msg.GameUpdate, None)
        

#------------------------------------------------------------------------------
# Main WebApp Code
#------------------------------------------------------------------------------

def main():
    url_map = [
        ('/request_newgame', RequestNewGameHandler),
        ('/UpdateRequest', UpdateRequestHandler),
        ('/UpdateResult', UpdateResultHandler),
        ('/SendResult', SendResultHandler),
    ]
    
    application = webapp.WSGIApplication(url_map, debug=True)
    wsgiref.handlers.CGIHandler().run(application)

if __name__ == '__main__':
    main()
